UserScriptをbundleするDeno script
一部のESModuleをまとめないでimportのままにする事もできる
既に自分のprojectにあるコードをimport先にすることができる
用途
たくさんのESModuleに分かれた他者のUserScriptを自分のprojectに移植するときに使う
すべてのファイルを手作業でコピペするのは地獄なので、一つにまとめたほうがいい
2021-06-08 22:21:21 script読み込みの高速化以外にメリットなくね?takker.icon
確かに自分のprojectにコピペすれば、そのscriptに誰かが悪意のあるコードを入れられなくなるが、すでに悪意のあるコードが混入している場合には無力
1ファイルずつ、自分で変なコードが混じっていないか確認する必要がある
既知の問題
dynamic importに相対パスを使っているとうまくコードが動かなくなる
workerのソースも同様
scrapboxのUserScriptの場合、../や/api/codeから始まるパスなら大丈夫
./で同じページ内のファイルを指定していたときに事故る
deno bundleだと↓はうまくコードを省いてくれるが、本ページのscriptだと余計なものまで含まれてしまう code:script.js
code:importmap.json
{
"imports": {
}
}
いやちゃんとtree shakingされた
気のせいだった?
reloadしなかったせい?
使い方
以下を実行する
code:sh
filename.js
bundleしたいscriptのentry point
localのファイルでもインターネット上のファイルでも可
--outfile
bundle後のJSファイルのファイル名
指定しないとコードが標準出力に出力される
パイプを使って | pbcopyや | xselをつなげれば、そのままクリップボードにコピーできる
--external
bundleに含めたくないESModuleのパス名
JSファイルに書き込まれているパス名をそのまま書く
相対パスで指定されていたら相対パスで、絶対パスで指定されていたら絶対パスで書く
挙動はただの文字列判定
import {...} from 'xxx'のxxxが--externalに指定した文字列と一致した場合、そのmoduleをbundleから除外する
--importMap
localファイルでもインターネット上のファイルでも可
あとはesbuildの引数と同じ
JSファイルに埋め込むときは--sourcemap=inlineにする
関連
実装したいこと
esbuildのオプション引数を全部羅列するのが面倒か
2022-02-01
13:52:06 コード雑だな……
直したいtakker.icon
2021-06-05
12:45:46
build errorが出てもcacheを消す
refactor: 型をつける
12:03:40
APIとCLIを分離した
esbuildのversionを固定した
2021-05-28
22:36:22 esbuildのcache directoryを消すようにした
2021-05-26
18:17:27 --import-mapを--importMapにした
18:08:33 --externalが一つのときにバグっていたのを直した
code
code:build.ts
import { run } from './script.ts';
const {_: entryFilePath, importMap, external, ...rest} = parse(Deno.args); if (typeof entryFilePath === 'number') throw Error('entryFilePath must be string');
if (importMap) {
if (/^https?:\/\//.test(importMap)) {
const res = await fetch(importMap);
imports = await res.json();
} else {
imports = JSON.parse(await Deno.readTextFile(importMap));
}
}
await run(entryFilePath, imports, {external, ...rest});
code:script.ts
// 特定のpropertyを削るやつ
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export async function run(filename: string, imports: {key: string: string}, {external, ...rest }: Omit<BuildOptions, 'entryPoints' | 'platform' | 'plugins'>) { let useTempFile = false;
let result: BuildResult | undefined = undefined;
try {
if (/^https?:\/\//.test(filename)) {
const tempname = index-${Math.random()}.ts;
await Deno.writeTextFile(tempname, import '${filename}';export * from '${filename}';);
filename = tempname;
useTempFile = true;
}
const options: BuildOptions = {
platform: 'neutral',
external: Array.isArray(external) ? external : (external ? external : []), ...rest
};
result = await build(options);
stop();
} catch(e) {
throw e;
}finally {
//後始末
if (useTempFile) await Deno.remove(filename);
if (await exists('./cache')) await Deno.remove('./cache', { recursive: true });
}
return result;
}